/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the ViewMyCurrency extension.
 *
 * The Initial Developer of the Original Code is
 * Will Moffat.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//dump("// $Id: WILL_PopupConvertToolbarManager.js,v 1.40 2007/03/26 09:30:08 Will Exp $;\n");


VMC.PopupConvertToolbarManager = function(viewMyCurrency,range,originalFocus) {
  var self=this;
  this.viewMyCurrency=viewMyCurrency;
  this.range=range;
  this.nodes=null;
  
  this.mode = null; // do we edit contents of 'CLIPBOARD' or 'WEBPAGE'
  
  this._originalFocus = originalFocus; 
  this.guiUpdate=guiUpdate;
  this._writeBackChanges = _writeBackChanges;
  this.setCurrencyTooltip = setCurrencyTooltip;
  this.guiUnload=guiUnload;
  this._init = _init;
  this._initTextBox = _initTextBox;
  this._getNewText = _getNewText;
  this._checkCurrency = _checkCurrency;
  this._fillCurrencyMenulist = _fillCurrencyMenulist;  
  this._countOccurrences = _countOccurrences;

  this._chompText = _chompText;
  this._unChompText = _unChompText;

  this.toolbar=VMC.ge('VMC.popupConvert');
  this._tip=VMC.ge('VMC.tip');
  this._previousOldTextBox = {
    text :           null, // a copy of oldTextBox.value to undo invalid user changes
    markerCount    : null,
    selectionStart : 0,    // caret starts at first char
    selectionEnd   : 0 };
  this.TEMP_ELEM_END_CHAR   = '\u2002'; // use EN space as marker
  this.TEMP_NODE_START_CHAR = '\u2003'; // use EM space as marker
  this.TEMP_ELEM_END_CHAR_RE   = /\u2002/g;
  this.TEMP_NODE_START_CHAR_RE = /\u2003/g;
  this.TEMP_NON_MARKERS_RE    = /[^\u2002\u2003]/g;
  this._init();
  //WILL: TODO: Horrible textbox.value hack below:
  window.setTimeout( function() {self._initTextBox(self.newTextBox, self._getNewText());} ,1); //wait 1ms and then update the new textbox
  //this.guiUpdate();

  function _initTextBox(box,text) {
    //text = text.replace(/\s/g,' '); // covert all whitespace to spaces (don't show vertical tabs)
    var newlines = this._countOccurrences(text,'\n'); //count the newlines
    var rows = 1 + newlines ; // we start with one line, each new line adds a row
    if (rows>VMC.PopupConvertToolbarManager.MAX_LINES) { rows=VMC.PopupConvertToolbarManager.MAX_LINES; } // never show more than 5 rows
    if (rows==1) {
       box.setAttribute('multiline',false);
    } else {
      box.setAttribute('multiline',true);
      box.setAttribute('rows',rows-1);  //WILL: TODO: it seems stupid that rows attribute is how many extra rows. Not total number
    }

    //WILL: TODO: Get rid of this horrible hack. Without the timeout the value doesn't change in the DOM (but is does in JS!) if multiline changes!
    window.setTimeout( function() { box.value=text; } ,1); //wait 1ms and then update the textbox
    //box.value=text;
  }

  function onBrowserMouseDown() { self.guiUnload(); }

  //Info about unicode spaces: http://www.cs.tut.fi/~jkorpela/chars/spaces.html
  //WILL: TODO: removing runs of whitespace will change the conversion results compared to a normal page convert. Normally the number of spaces is counted and the closest number/currency symbol pair is chosen.
  function _chompText(text) {

    var chompedText=text;
    // WILL: TODO: if I can use these special markers here, why can't I use them in HtmlConversion?? I use them here because they look better in a textbox
    // WILL: TODO: add check for existing marker chars
    // Important: They must be part of the RegExp whitespace class /s
    // Confirmed here: http://hamstersoup.com/javascript/regexp_character_class_tester.html
    chompedText = chompedText.replace(/[ \n\t]+/g,' '); // runs of normal spaces, newlines and tabs (doesn't affect HTML) and replace with a single space
    chompedText = chompedText.replace(VMC.HtmlConversion.ELEM_END_CHAR_RE,   this.TEMP_ELEM_END_CHAR);
    chompedText = chompedText.replace(VMC.HtmlConversion.NODE_START_CHAR_RE, this.TEMP_NODE_START_CHAR);

    //var lineLength = this.oldTextBox.width/10; //WILL: TODO: would be nice to calculate width
    var lineLength = 50;

    text='';
    for (var i=0;i<chompedText.length; i += lineLength) {
      if (i) { text += '\n'; }
      text += chompedText.substr(i,lineLength);
    }
    
    return text;
  }

  function _countOccurrences(str,c) {
    var count = 0;
    var pos = str.indexOf(c);
    while ( pos != -1 ) {
      count++;
      pos = str.indexOf(c,pos+1);
    }
    return count;
  }

  function _unChompText(text) {
    text = text.replace(/\n/g,''); // remove added newlines
    text = text.replace(this.TEMP_ELEM_END_CHAR_RE,   VMC.HtmlConversion.ELEM_END_CHAR);
    text = text.replace(this.TEMP_NODE_START_CHAR_RE, VMC.HtmlConversion.NODE_START_CHAR);
//     var unChompFunc = function(match) { return VMC.spaces.shift() };
//     text = text.replace(/\u200b/g,unChompFunc);
    return text;
  }

  function _init() {
    this.fromCurrBox = VMC.ge('VMC.PopupFromCurr');
    this.toCurrBox   = VMC.ge('VMC.PopupToCurr');
    this.oldTextBox  = VMC.ge('VMC.PopupOldText');
    this.newTextBox  = VMC.ge('VMC.PopupNewText');

    getBrowser().addEventListener("mousedown", onBrowserMouseDown, false); // hide the toolbar if we click outside it

    var selectedText;

    if (this.range && !(range.startContainer==range.endContainer && range.startOffset==range.endOffset)) {
      // text has been highlighted on the webpage
      this.nodes   = VMC.HtmlConversion._getAllNodesInRange(this.range);
      selectedText = VMC.HtmlConversion._getTextOfNodes(this.nodes,this.range.startOffset,this.range.endOffset);
      this.mode='WEBPAGE'; // there is text to modify on the web page
      selectedText = this._chompText(selectedText); // remove extra whitespace and make the markers invisible
    } else {
      selectedText = readFromClipboard();
      if (!selectedText || this._countOccurrences(selectedText,'\n')>1 || selectedText.length>30) {
	this.mode='CALCULATOR'; // don't modify either clipboard or current webpage
	selectedText='';
	// if the user has a lot of selected text, or lots of selected lines then don't use the clipboard contents.
	// (it's really confusing for a new user to see a huge popup bar full of strange text)
      } else {
	this.mode='CLIPBOARD'; // modify the contents of the clipboard
      }
    }
    
    if (!selectedText) { selectedText=''; } // if empty use zero-length string rather than void

    this._initTextBox(this.oldTextBox,selectedText);
    this._previousOldTextBox.text=selectedText; // make a copy of oldTextBox.value to undo invalid user changes

    //WILL: TODO: should _sourceCurrencyList and _targetCurrencyList be private??
    this._fillCurrencyMenulist(this.fromCurrBox, this.viewMyCurrency._sourceCurrencyList, 'VMC.lastFromCurrencyCode');
    this._fillCurrencyMenulist(this.toCurrBox,   this.viewMyCurrency._targetCurrencyList, 'VMC.lastToCurrencyCode'); 


    this.toolbar.hidden=false;


    //give text box the focus but don't select all text - too easy to destroy exisiting text
    var delayedFunc = function() {
      var oldTextBox = VMC.ge('VMC.PopupOldText');
	oldTextBox.setSelectionRange(0,0); 
      oldTextBox.focus();
    };

    window.setTimeout( delayedFunc ,1); //wait 1ms and then update the textbox
  }

  // rebuild menulist from scratch - it's possible currency list has changed
  // place the last used option on top
  function _fillCurrencyMenulist(currencyBox,currencyList,lastAttribute) {
    currencyBox.removeAllItems();

    var addnone = function() {
      if (currencyBox !== self.fromCurrBox) { return; } // it's only added to From:
      var offItem = currencyBox.appendItem('none');
      offItem.setAttribute('style','color: grey;');
    };

    var lastCurr=this.toolbar.getAttribute(lastAttribute);

    // if the last currency we used is still a valid currency then put it first in the list
    if (lastCurr) {
      if (lastCurr=='none') { addnone();}
      else {
	var currency=currencyList.findFromCode(lastCurr,true); //failSilently
	if (currency) {
	  currencyBox.appendItem(lastCurr);
	}
      }
    }

    var currencies=currencyList.items;
    for (var i=0;i<currencies.length;i++) {
      if (lastCurr != currencies[i].code) {
	currencyBox.appendItem(currencies[i].code);
      }
    }

    if (lastCurr != 'none') { addnone(); }

    currencyBox.selectedIndex=0; //select the 1st item (the remembered one)
  }

  function setCurrencyTooltip(box,currencyList,message) {
    var code = box.value;
    if (!code) { VMC.log('ERR','Missing code');  }
    if (code=='none') { message='No conversion. (You can use this to edit the web page)'; }
    else {
      var currency = currencyList.findFromCode(code, true); //failSilently
      var currencyDescription = currency ? currency.description : "Unknown currency code";
      message = currencyDescription + ' ' +message;
    }
    box.setAttribute('tooltiptext',message);
  }

  function _writeBackChanges() {
    var newText = this.newTextBox.value;
    switch (this.mode) {
    case 'WEBPAGE' : 
      newText = this._unChompText(newText);
      VMC.log('INFO','saving text to web page: ['+newText+']');
      VMC.HtmlConversion._setTextOfNodes(this.nodes,newText,this.range.startOffset,this.range.endOffset, true /*forceUpdateEmpty*/ );
      break;
    case 'CLIPBOARD' :
      VMC.log('INFO','saving text to clipboard: ['+newText+']');
      VMC.copyToClipboard(newText);
      break;
    case 'CALCULATOR' :
      // do not write changes anywhere
      break;
    default :
      VMC.alert('Unknown popup covert mode '+this.mode);
    }
  }

  // called on every keystroke and mouseclick
  //fromCurrBox is the SOURCE currency
  function guiUpdate(message,event) {
    //VMC.log('DBG',message+' event='+event);
    var newText;
    var oldTextBox = this.oldTextBox;
    var prev = this._previousOldTextBox;
    var curr = { selectionStart: oldTextBox.selectionStart, selectionEnd : oldTextBox.selectionEnd };

    //vmc.hack('message='+message+'\t target='+event.target.id);
    if (message=='fromChanged') {
      this.setCurrencyTooltip(this.fromCurrBox, this.viewMyCurrency._sourceCurrencyList, 'is default source currency');
    } else if (message == 'toChanged') {
      this.setCurrencyTooltip(this.toCurrBox,   this.viewMyCurrency._targetCurrencyList, 'is destination currency'); 
    }
    
    // note that a keypress will almost certainly fire an input event too (and the input event comes first)
    if (message=='onkeyup') {
      if (event.keyCode == event.DOM_VK_ESCAPE) {
	this.guiUnload();
	return;
      } else if (event.keyCode == event.DOM_VK_RETURN && event.ctrlKey === false && event.shiftKey === false ) {
	this._writeBackChanges();
	this.guiUnload();
	return;
      }
    }

   if (message=='input') {
      var updatedValue = oldTextBox.value;
      var numCharsInserted = updatedValue.length - prev.text.length;

      //WILL: TODO: the user should not be able to add marker chars (e.g. via copy and paste)
      if (numCharsInserted==1) {
	var charInserted = updatedValue[curr.selectionStart-1];
	if (charInserted == '\n') { return; } // pressing return should finish popup, not insert a newline. 'onkeyup' will handle it
      }

      if (this.mode=='WEBPAGE') { // make sure that the invisable markers are not deleted

	var nothingWasSelected = (prev.selectionStart == prev.selectionEnd);
	
	if (!nothingWasSelected || numCharsInserted<0) {   // if there was previously highlighted text or we pressed delete
	  // check what got replaced/deleted and add any missing marker chars
	  var changed;
	  if (nothingWasSelected) { changed = prev.text[curr.selectionStart]; } // get the old char at CURRENT caret position
	  else                    { changed = prev.text.slice(prev.selectionStart,prev.selectionEnd); } // get the PREVIOUSLY highlighted text
	  var deletedMarkers = changed.replace(self.TEMP_NON_MARKERS_RE,''); // remove everthing that isn't a marker
	  if (deletedMarkers.length>0) {
	    var left=updatedValue.slice(0,curr.selectionStart);
	    var right=updatedValue.slice(curr.selectionStart);
	    oldTextBox.value = left + deletedMarkers + right;
	  }
	}

	prev.text=oldTextBox.value; // make a copy of oldTextBox.value to undo invalid user changes
      }

      //WILL: TODO: FUTURE: Escape pressed anywhere should close (not just oldTextBox)
      //WILL: TODO: FUTURE: when the focus is in one of the listboxes but the popup list isn't displayed then return should update and close the toolbar

      oldTextBox.setSelectionRange(curr.selectionStart,curr.selectionEnd);

    }

    // store the OldTextBox current selction even on mouse events, so that we can undo a cut or replace that removes markers
    prev.selectionEnd = curr.selectionEnd;
    prev.selectionStart = curr.selectionStart;

    // convert the old text into new text by doing currency conversion on any numbers that appear
    this.newTextBox.value=this._getNewText();

  
  }
  
  //WILL: there should be a nicer way to do the conversion. Deep accesses into viewMyCurrency is bad OOP
  function _getNewText() {
    var oldText = this.oldTextBox.value;    
    try {
      var newText=null;
      // create tooltip and throw exception if the source and target aren't valid
      var targetCurrency = this._checkCurrency(this.viewMyCurrency._targetCurrencyList, this.toCurrBox);
      if (targetCurrency) {
	var conversionProfile=this.viewMyCurrency.conversionProfileTable._activeProfileEntry.profile;
	if (!conversionProfile) {
	   // we are on an excluded page but the user still wants to do a popup convert, so use the defaultEntry
	   // (profile is automatically restored on next page load or click to change target.)
	   this.viewMyCurrency.conversionProfileTable.setActiveProfileEntryToDefault();
	   conversionProfile=this.viewMyCurrency.conversionProfileTable._activeProfileEntry.profile;
	   //show the new profile in the GUI. 
	   this.viewMyCurrency.updateGuiCallback("PROFILE",this.viewMyCurrency.conversionProfileTable._activeProfileEntry.description);
	}
	var sourceCurrency = this._checkCurrency(this.viewMyCurrency._sourceCurrencyList, this.fromCurrBox); 
	
	var getRateFunc = this.viewMyCurrency.getRateFunc;
	newText = VMC.NumberConversion.prototype.currencyConvertStr(oldText,targetCurrency, conversionProfile, getRateFunc,
								     this.viewMyCurrency.hideOriginalCurrency, sourceCurrency );
      }
      if (!newText) {
	//WILL: normally this is an error, but it can also happen if nothing changed throw "conversion error"; 
	newText=oldText;
      }


      //       this.newBox.setAttribute('style','color:green;');
      
      //WILL: TODO: only hide if visiable - causes exeptions
      this._tip.hidePopup();
    } catch (error) {
      VMC.log('DBG',error);
      
    }
    return newText;
  }
  

  function _checkCurrency(currencyList,currencyBox) {
    var currencyCode = currencyBox.value;
    if (currencyCode=='none') { return null; }

    var currency=currencyList.findFromCode(currencyCode, true/*silentFailure*/);
    //if (sourceCurrency.equals(targetCurrency)) { throw "Source == target"; }
    if (!currency) {
      //WILL: TODO: make a different tool tip ??
      var errorMsg = 'Invalid currency symbol';
      // highlight in red if invalid
      this._tip.firstChild.value=errorMsg;
      this._tip.showPopup(currencyBox,-1,-1,"tooltip","topleft","bottomleft");
      //this._setText(this.newBox,'error');
      //this.newBox.setAttribute('style','color:red;');

      throw errorMsg;
    }
    return currency;
  }



  function guiUnload() {
    //alert('unloading');
    getBrowser().removeEventListener("mousedown", onBrowserMouseDown, false);
    this.toolbar.setAttribute('VMC.lastFromCurrencyCode',this.fromCurrBox.value);
    this.toolbar.setAttribute('VMC.lastToCurrencyCode',  this.toCurrBox.value);
    this.toolbar.hidden=true;
    if (this._originalFocus) { this._originalFocus.focus(); } // restore the original focus
  }

};

VMC.PopupConvertToolbarManager.MAX_LINES = 5;
